#include "SharedMemory.hpp"
#include <fcntl.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>

#include "hiScopeGuard.h"
#include "HiCommonLog.hpp"

#include <sstream>

using namespace std;

static const char* EMPTY_SUFFIX = ".empty";
static const char* FULL_SUFFIX = ".full";

static string &replace_all(std::string &str, const std::string &old_value, const std::string &new_value);
static int sem_timedwait_millsecs(sem_t *sem, long msecs);

SharedMemory::Error SharedMemory::Init(const char* path, uint32_t size, bool semaph) {	
	auto ret = OpenAndInitFile(path, size);
	if (Error::None != ret) {
		return ret;
	}
	
	HiScopeGuard scope_fd([&](){
		::close(this->fd);
		this->fd = -1;
	});

	this->ptr = (uint8_t*)::mmap(NULL, this->sharedSize, PROT_READ | PROT_WRITE, MAP_SHARED, this->fd, 0);

	if ((void *)ptr == (void *)-1) {
		/*
		 *  On success, mmap() returns a pointer to the mapped area.  On error,
		 *  the value MAP_FAILED (that is, (void *) -1) is returned, and errno is
		 *  set to indicate the cause of the error.
		 */
		 
		printf("::mmap failed\n");
		return Error::MmapFail;
	}
	
	HiScopeGuard scope_ptr([&](){		
		if (nullptr == this->ptr) {
			return;
		}
		
		::munmap(this->ptr, this->sharedSize);
		::unlink(path);
		this->ptr = nullptr;
	});
	
	this->semaphore = semaph;
	if (!this->semaphore) {
		return Error::None;
	}
	
	ret = this->CreateSems();
	if (IsSuccess(ret)) {
		scope_ptr.dismissed();
		scope_fd.dismissed();
	}
	
	return ret;
}

void SharedMemory::Write(const uint8_t *data, uint32_t len)
{
	if (len > sharedSize) {
		return;
	}

	if (!this->semaphore) {
		::flock(this->fd, LOCK_EX);
		::memcpy(this->ptr, data, len);
		::flock(this->fd, LOCK_UN);
		return;
	}
	
	auto res = ::sem_timedwait_millsecs(this->pempty, 1000);
	if (res < 0){
		// MS_DUMP("error...");
		HI_DEBUG("sem_timedwait_millsecs timeout");
		return;
	}
	
	// MS_DUMP("write ...");
	::flock(this->fd, LOCK_EX);
	::memcpy(this->ptr, data, len);
	::flock(this->fd, LOCK_UN);
	// MS_DUMP("writed ...");
	::sem_post(this->pfull);
	// MS_DUMP("posted ...");
}

SharedMemory::SharedMemory() {
}

SharedMemory::~SharedMemory()
{
	if (this->fd != -1) {
		::close(this->fd);
		this->fd = -1;
	}
	
	if (this->ptr != nullptr) {
		::munmap(ptr, sharedSize);
		::unlink(sharedPath.c_str());
		this->ptr = nullptr;
	}

	if (this->pempty != nullptr)
	{
		::sem_close(this->pempty);
		this->pempty = nullptr;
	}
	if (this->pfull != nullptr)
	{
		::sem_close(this->pfull);
		this->pfull = nullptr;
	}
}


SharedMemory::Error SharedMemory::OpenAndInitFile(const char* path, uint32_t size) {
	this->fd = ::open(path, O_CREAT | O_RDWR | O_TRUNC, 00777);
	if (this->fd < 0) {
		return Error::OpenFileFail;
	}
	
	this->sharedPath = path;
	this->sharedSize = size;

	::lseek(this->fd, this->sharedSize, SEEK_SET);
	::write(this->fd, "", 1);
	return Error::None;
}

SharedMemory::Error SharedMemory::CreateSems() {
	string newPath = this->sharedPath;
	newPath = replace_all(newPath, "/", ".");
	
	stringstream oss;
	oss << "/" + newPath;
	
	string empty = oss.str() + EMPTY_SUFFIX;
	string full = oss.str() + FULL_SUFFIX;

	// MS_DUMP("open empty semaphore %s", empty.c_str());
	// MS_DUMP("open full semaphore %s", full.c_str());
	this->pempty = ::sem_open(empty.c_str(), O_CREAT, 0644, 1);
	if (pempty == NULL) {
		//HI_DEBUG("sem_open() empty failed for error %s", strerror(errno));
		printf("sem_open() empty failed for error %s\n", strerror(errno));
		return Error::SemFail;
	}
	printf("empty url:%s\n", empty.c_str());
	
	HiScopeGuard scope_pempty([&](){
		::sem_close(this->pempty);
		this->pempty = nullptr;
	});
	this->pfull = ::sem_open(full.c_str(), O_CREAT, 0644, 0);
	if (pfull == NULL) {
		//HI_DEBUG("sem_open() full failed for error %s", strerror(errno));
		printf("sem_open() full failed for error %s\n", strerror(errno));
		return Error::SemFail;
	}
	printf("full url:%s\n", full.c_str());
	
	scope_pempty.dismissed();
	return Error::None;
}

static int sem_timedwait_millsecs(sem_t *sem, long msecs)
{
  struct timespec ts;
  clock_gettime(CLOCK_REALTIME, &ts);
  long secs = msecs / 1000;
  msecs = msecs % 1000;

  long add = 0;
  static long million = 1000 * 1000;
  msecs = msecs * million + ts.tv_nsec;
  static long billion = 1000 * 1000 * 1000;
  add = msecs / billion;
  ts.tv_sec += (add + secs);
  ts.tv_nsec = msecs % billion;

  return sem_timedwait(sem, &ts);
}

static std::string &replace_all(std::string &str, const std::string &old_value, const std::string &new_value)
{
  while (true)
  {
    std::string::size_type pos(0);
    if ((pos = str.find(old_value)) != std::string::npos)
      str.replace(pos, old_value.length(), new_value);
    else
      break;
  }
  return str;
}
